﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Linq;
using Microsoft.SharePoint.Utilities;
using ZhukBlogLinqExamples.Model;
using System.Net;

namespace ZhukBlogLinqExamples.Repositories
{
    public abstract class WebServiceBaseRepository<TEntity> where TEntity : ZhukDataItem, new()
    {
        private readonly string _serviceUrl;
        private readonly string _listName;
        private readonly ICredentials _credentials;

        protected WebServiceBaseRepository(string webUrl, string listName) : this(webUrl, listName, null) { }

        protected WebServiceBaseRepository(string webUrl, string listName, ICredentials credentials)
        {
            _serviceUrl = SPUtility.ConcatUrls(webUrl, "/_vti_bin/Lists.asmx");
            _listName = listName;
            _credentials = credentials;

        }

        public IEnumerable<TEntity> GetEntryCollection(XmlNode query, int rowLimit)
        {
            var res = new List<TEntity>();
            using (var client = new ListsReference.Lists())
            {
                var lists = new ListsReference.Lists();
                lists.PreAuthenticate = true;
                if (_credentials != null)
                {
                    lists.Credentials = _credentials;
                }
                else
                {
                    lists.UseDefaultCredentials = true;
                    lists.Credentials = CredentialCache.DefaultCredentials;
                }
                client.Url = _serviceUrl;
                
                var options = new XElement("QueryOptions",
                                           new XElement("IncludeAttachmentUrls", false));
                var fields = (new TEntity()).ViewFields();
                var itemsXml = client.GetListItems(_listName, string.Empty, query, fields,
                                                   rowLimit.ToString(),
                                                   options.GetXmlNode(), string.Empty);
                var resXml = itemsXml.ChildNodes.Cast<XmlNode>().First(n => n.Name == "rs:data");
                var qnt = Convert.ToInt32(resXml.Attributes["ItemCount"].Value);
                if (qnt > 0)
                {
                    foreach (var node in itemsXml.ChildNodes.Cast<XmlNode>().Where(node => node.Name == "rs:data"))
                    {
                        res = node.ChildNodes
                            .Cast<XmlNode>()
                            .Where(n => n.Name == "z:row")
                            .Select(a => GetItem(a))
                            .ToList();
                    }
                }
            }
            return res;
        }

        public IEnumerable<TEntity> GetEntryCollection(int rowLimit)
        {
            var query = new XElement("Query");
            return GetEntryCollection(query.GetXmlNode(), rowLimit);
        }

        public IEnumerable<TEntity> GetEntryCollection()
        {
            return GetEntryCollection(int.MaxValue);
        }

        private static TEntity GetItem(XmlNode xml)
        {
            var res = new TEntity();
            if (xml.Attributes == null) return res;
            var objType = res.GetType();
            var properties = objType.GetProperties();
            foreach (var property in properties)
            {
                var attributes = property.GetCustomAttributes(typeof(ColumnAttribute), false);
                foreach (ColumnAttribute att in attributes)
                {
                    var field = objType.GetField(att.Storage,
                                                 BindingFlags.NonPublic | BindingFlags.Instance);
                    while (field == null)
                    {
                        objType = objType.BaseType;
                        if (objType == null) break;
                        field = objType.GetField(att.Storage,
                                                 BindingFlags.NonPublic | BindingFlags.Instance);
                    }
                    if (field != null)
                    {
                        switch (att.FieldType)
                        {
                            case "Lookup":
                            case "User":
                                try
                                {
                                    var fv = new SPFieldLookupValue(
                                        (xml.Attributes["ows_" + att.Name].Value ?? string.Empty));
                                    if (att.IsLookupId)
                                    {
                                        field.SetValue(res, fv.LookupId);
                                    }
                                    else
                                    {
                                        field.SetValue(res, fv.LookupValue);
                                    }
                                }
                                catch (ArgumentException)
                                {
                                    field.SetValue(res, xml.Attributes["ows_" + att.Name].Value);
                                }
                                break;
                            case "Guid":
                                field.SetValue(res, new Guid(xml.Attributes["ows_" + att.Name].Value));
                                break;
                            default:
                                field.SetValue(res, xml.Attributes["ows_" + att.Name].Value);
                                break;
                        }
                    }
                }
            }
            return res;
        }
    }
}
